//
// Created by 33145 on 2023/6/6.
//

#include <sys/socket.h>
#include <unistd.h>
#include <cstring>

#include "tcp_client.h"
#include "../common/rocket.h"
#include "../net/fdevent_group.h"

namespace rocket {
#define ERROR_PEER_CLOSED 1;
#define ERROR_FAILED_CONNECT 2;

  TCPClient::TCPClient(const NetAddr::ptr& peer_addr) : _peer_addr(peer_addr) {
    _event_loop = EventLoop::GetCurrentEventLoop();
    _fd = socket(peer_addr->getFamily(), SOCK_STREAM, 0);
    if (_fd < 0) {
      LOG_ERROR("TcpClient::TcpClient() error, failed to create fd");
      return;
    }
    _fd_event = FdEventGroup::GetFdEventGroup()->getFdEvent(_fd);
    _fd_event->setNonBlock();  // 设置非阻塞，方便执行异步操作
    // 此时还没有本地地址，只能传nullptr
    _connection = std::make_shared<TCPConnection>(_event_loop, _fd, 128, peer_addr, nullptr, TCPConnection::TcpConnectionType::TcpConnectionByClient);

  }

  TCPClient::~TCPClient() {
    LOG_DEBUG("TcpClient::~TcpClient()");
    if (_fd > 0) {
      close(_fd);
    }
  }

  // 异步的进行 connect 如果connect 成功，done 会被执行
  void TCPClient::connect(const std::function<void()>& done) {
    int rt = ::connect(_fd, _peer_addr->getSockAddr(), _peer_addr->getSockLen());
    if (rt == 0) {
      LOG_DEBUG("connect [%s] success", _peer_addr->toString().c_str());
      _connection->setState(TCPConnection::Connected);
      initLocalAddr();
      if (done) {
        done();
      }
    } else if (rt == -1) {
      if (errno == EINPROGRESS) {
        // epoll 监听可写事件，然后判断错误码  注册写事件回调
        _fd_event->listen(FdEvent::OUT_EVENT,
                          [this, done]() {
                            int rt = ::connect(_fd, _peer_addr->getSockAddr(), _peer_addr->getSockLen());
                            if ((rt < 0 && errno == EISCONN) || (rt == 0)) {  // 连接成功
                              LOG_DEBUG("connect [%s] success", _peer_addr->toString().c_str());
                              initLocalAddr();
                              _connection->setState(TCPConnection::Connected);
                            } else {  // 连接被拒绝 可能是对端端口未开启
                              if (errno == ECONNREFUSED) {
                                _connect_error_code = ERROR_PEER_CLOSED;
                                _connect_error_info = "connect refused, sys error = " + std::string(strerror(errno));
                              } else {
                                _connect_error_code = ERROR_FAILED_CONNECT;
                                _connect_error_info =
                                        "connect unknown error, sys error = " + std::string(strerror(errno));
                              }
                              LOG_ERROR("connect error, errno=%d, error=%s", errno, strerror(errno));
                              close(_fd);
                              _fd = socket(_peer_addr->getFamily(), SOCK_STREAM, 0);  // 为什么要换一个新的socket？
                            }

                            // 连接完后需要去掉可写事件的监听，不然会一直触发
                            _fd_event->cancel(FdEvent::OUT_EVENT);  // TODO 修改为addEpollEvent
                            _event_loop->addEpollEvent(_fd_event.get());
                            LOG_DEBUG("now begin to done");
                            // 如果连接完成，才会执行回调函数
                            if (done) {
                              done();
                            }
                          }
        );
        _event_loop->addEpollEvent(_fd_event.get());

        if (!_event_loop->isLooping()) {
          _event_loop->loop();
        }
      } else {
        LOG_ERROR("connect error, errno=%d, error=%s", errno, strerror(errno));
        _connect_error_code = ERROR_FAILED_CONNECT;
        _connect_error_info = "connect error, sys error = " + std::string(strerror(errno));

      }
    }
  }

  void TCPClient::writeMessage(const AbstractProtocol::ptr& message, const std::function<void(AbstractProtocol::ptr)>& done) {
    // 将message和回调写入connection
    // 监听
    _connection->pushSendMessage(message, done);
    _connection->listenWrite();
  }

  void TCPClient::readMessage(const std::string &msg_id, std::function<void(AbstractProtocol::ptr)> done) {
    // 监听可读事件  从buffer中decode得到Abstract对象
    _connection->pushReadMessage(msg_id, done);
    _connection->listenRead();
  }

  void TCPClient::stop() {
    if (_event_loop->isLooping()) {
      _event_loop->stop();
    }
  }

  int TCPClient::getConnectErrorCode() {
    return _connect_error_code;
  }

  std::string TCPClient::getConnectErrorInfo() {
    return _connect_error_info;
  }

  NetAddr::ptr TCPClient::getPeerAddr() {
    return _peer_addr;
  }

  NetAddr::ptr TCPClient::getLocalAddr() {
    return _local_addr; // TODO 可以用接口获得本地连接地址
  }

  void TCPClient::initLocalAddr() {
    sockaddr_in local_addr;
    socklen_t addr_len;
    int ret = getsockname(_fd, (sockaddr*)&local_addr, &addr_len);
    if (ret != 0) {
      LOG_ERROR("get local addr error [%s]", strerror(errno));
      return;
    }
    _local_addr = std::make_shared<IPNetAddr>(local_addr);
  }

  void TCPClient::addTimerEvent(const TimerEvent::ptr& timer_event) {
    _event_loop->addTimerEvent(timer_event);
  }
}